package quarto.controller.client;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;

import quarto.Message;
import quarto.model.Board;
import quarto.controller.Protocol;
import quarto.model.Piece;
import quarto.view.client.ClientGUI;
import quarto.view.client.ClientUI;

/**
 * Klasse die de communicatie tussen de player en de UI regelt
 * @knows
 * - De Player, het Board en de ClientUI
 * @does
 * - Verbinding maken met server
 * - Input van de server doorsturen naar Player
 * - Output van de player doorsturen naar ClientHandler
 * @author Frank Borgonjen & Tim Smit
 */
public class Client extends Thread implements Protocol
{
    /** computer speler */
    public static final String COMPUTER = "computer";
    
    /** menselijke speler */
    public static final String HUMAN    = "mens";
    
    /** de speler */
    private Player player;
    
    /** het bord */
    private Board board;
    
    /** de 'user interface' */
    private ClientUI ui;
    
    /** de naam van de speler */
    private String clientName;
    
    /** het communicatie socket */
    private Socket sock;
    
    /** de invoerstream */
    private BufferedReader in;
    
    /** de uitvoerstream */
    private BufferedWriter out;
    
    /** is de client "up-and-runnin'" */
    private boolean running;
    
    /**
     * Construeert een Client-object en probeert een socketverbinding
     * met een Server op te starten.
     */
    public Client(String name, InetAddress host, int port, ClientUI ui, Player player) throws IOException
    {
        this.ui         = ui;
        this.clientName = name;
        this.player     = player;
        this.board      = new Board();

        // probeer een socket te openen naar de server
        try
        {
            this.sock = new Socket(host, port);   
        }
        // er kan geen verbinding gemaakt worden
        catch (IOException e)
        {
            throw new IOException(String.format(Message.CLIENT_ERROR_CONNECTING, host, port));
        }
        // poort buiten bereik
        catch (IllegalArgumentException e)
        {
            throw new IllegalArgumentException(Message.CLIENT_ERROR_OUT_OF_RANGE);
        }
        
        // als de socket opgestart is
        if (this.sock != null)
        {
            // initialiseert de data streams
            try
            {
                this.in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
                this.out = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));

                // Spel joinen
                this.sendMessage(JOIN + DELIM + TRUE + DELIM + clientName);
            }
            // initialiseren lukt niet
            catch (IOException e)
            {
                throw new IOException(Message.CLIENT_ERROR_STREAM_INIT);
            }

            // even aangeven dat we up and running zijn
            this.running = true;
        }
    }
    
    /**
     * Geeft het bord terug
     * @return het bord
     */
    public Board getBoard()
    {
        return this.board;
    }
    
    /**
     * Laat de ClientUI het bord updaten
     */
    public void showBoard()
    {
        this.ui.showBoard();
    }

    /**
     * Stuurt een commando via de output stream naar de ClientHandler
     * @param msg het commando dat verstuurd wordt
     */
    public void sendMessage(String msg)
    {
        try
        {
            this.out.write(msg);
            this.out.newLine();
            this.out.flush();
        }
        catch (IOException e)
        {
            this.errorMessage(Message.CLIENT_ERROR_SEND_ERROR);
        }
    }

    /**
     * Wordt aangeroepen bij het starten van de Thread.
     * In een oneindige loop wordt er gekeken of er data op de input stream
     * is komen te staan. Zodra er data opstaat moet deze geinterpreteerd
     * en uitgevoerd worden.
     */
    @Override
    public void run()
    {
        String s = null;
        try
        {
            while (true)
            {
                // checken of er nog wat te lezen is.
                s = in.readLine();
                if (s != null)
                {
                    this.player.execute(s);
                }
                else
                {
                    throw new IOException();
                }
            }
        }
        // fout, dus afsluiten
        catch (IOException e)
        {
            this.shutdown();
        }
    }
    
    /**
     * Sluit de input stream, output stream en de socket verbinding.
     * ClientHandler merkt vervolgens dat de verbinding verbroken is en geeft
     * aan de server door dat de client vertrokken is.
     */
    public void shutdown()
    {
        if (this.running)
        {
            // afgesloten, dus niet meer aan het runnen
            this.running = false;
            
            //this.player.stopThreads();
            this.player = null;
            
            this.addMessage(Message.CLIENT_CLOSING);
            try
            {
                this.out.close();
                this.in.close();
                this.sock.close();
            } 
            catch (IOException e)
            {
                this.errorMessage(Message.CLIENT_ERROR_CLOSING);
            }
            this.addMessage(Message.CLIENT_CLOSED);
            
            this.askForReconnect();
        }
    }
    
    /**
     * Geeft een bericht door aan de UserInterface van de client
     * @param msg het bericht dat verstuurd wordt
     */
    public void addMessage(String msg)
    {
        this.ui.addMessage(msg);
    }
    
    /**
     * Geeft een foutmelding door aan de UserInterface van de client
     * @param msg de foutmelding dat verstuurd wordt
     */
    public void errorMessage(String msg)
    {
        this.ui.errorMessage(msg);
    }
    
    /**
     * Vraagt aan de gebruiker of hij nog een keer wilt connecten
     */
    public void askForReconnect()
    {
        this.ui.askForReconnect();
    }

    /**
     * Vraag aan de UI welke locatie voor het opgegeven stuk gebruikt moet worden
     * @param het stuk
     * @return de locatie
     */
    public int getLocationForPiece(Piece piece)
    {
        return this.ui.getLocationForPiece(piece);
    }
    
    /**
     * Vraag aan de UI welk stuk de speler voor de tegenstander wil kiezen
     * @return het stuk
     */
    public Piece getNewPiece()
    {
        return this.ui.getNewPiece();
    }
    
    /**
     * Geeft aan onder welke namen het spel gespeeld wordt indien de UI een GUI is.
     * @param yourName de naam van deze speler
     * @param opponentName de naam van de tegenstander
     */
    public void setPlayerNames(String yourName, String opponentName)
    {
        if (this.ui instanceof ClientGUI)
        {
            ((ClientGUI)this.ui).setPlayerNames(yourName, opponentName);
        }
    }
    
    /**
     * Indien de UI een GUI is, moet het het huidige stuk laten zien
     * @param piece het huidig stuk
     */
    public void showCurrentPiece(Piece piece)
    {
        if (this.ui instanceof ClientGUI)
        {
            ((ClientGUI)this.ui).showCurrentPiece(piece);
        }
    }
    
    /**
     * Indien de UI een GUI is, zal het stuk voor de 
     * tegenstander zichtbaar worden
     * @param piece het stuk voor de tegenstander
     */
    public void showOpponentPiece(Piece piece)
    {
        if (this.ui instanceof ClientGUI)
        {
            ((ClientGUI)this.ui).showOpponentPiece(piece);
        }
    }
    
    /**
     * Zet de speler voor de client
     * @param player
     */
    public void setPlayer(Player player)
    {
        this.player = player;
    }
}
